/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.jeff.springWebTestNG.concurrent;

import java.util.concurrent.*;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author zhou.zhefu
 */
public class CancellationEmulator {
    private static final long startTime = System.currentTimeMillis();
    private ScheduledExecutorService cancelExec = Executors.newScheduledThreadPool(2);
    private ExecutorService taskExec = Executors.newFixedThreadPool(2);
    
    
    public void startJoinCancel() {
        Task task = new Task(1);
        long timeout = 20;
        TimeUnit unit = TimeUnit.MILLISECONDS;
        try {
            timeRun1(task, timeout, unit);
        } catch (InterruptedException ex) {
            Logger.getLogger(CancellationEmulator.class.getName()).log(Level.SEVERE, null, ex);
        } catch (Throwable t) {
            Logger.getLogger(CancellationEmulator.class.getName()).log(Level.WARNING, null, t);
        }
    }
    
    /**
     * This implementation create primitive thread and manage the life cycle manually, kind of 'reinventing wheel ', 
     * and an 'OK' design of cancellation, which inheritates the weakness of 'join()'
     * @param r
     * @param timeout
     * @param unit
     * @throws InterruptedException
     * @throws Throwable 
     */
    private void timeRun1(final Runnable r, long timeout, TimeUnit unit) throws InterruptedException, Throwable {
        class RethrowTask implements Runnable {
            Throwable t;
            @Override
            public void run() {
                try {
                    r.run();
                } catch (Throwable t) {
                    this.t = t;
                }
            }
            
            void rethrow() throws Throwable {
                if (t != null) throw t;
            }
        }
        
        final RethrowTask task = new RethrowTask();
        final Thread taskThread = new Thread(task);
        taskThread.start();
        
        cancelExec.schedule(new Runnable() {
            @Override
            public void run() {
                taskThread.interrupt();
            }
        }, timeout, unit);
        
        taskThread.join(timeout);
        task.rethrow();
    }
    
    
    public void startFutureCancel() {
        Task task = new Task(2);
        long timeout = 20; //change this value to switch between complete/timout result
        TimeUnit unit = TimeUnit.MILLISECONDS;
        try {
            timeRun2(task, timeout, unit);
        } catch (ExecutionException ex) {
            Logger.getLogger(CancellationEmulator.class.getName()).log(Level.SEVERE, null, ex);
        } catch (InterruptedException ex) {
            Logger.getLogger(CancellationEmulator.class.getName()).log(Level.SEVERE, null, ex);
        } finally {
            System.out.println("The End");
        }
    }
    
    /**
     * This implementation functions similar to timeRun1(), but much simpler with using Future
     * @param r
     * @param timeout
     * @param unit 
     */
    private void timeRun2(final Runnable r, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException {
        final Task rr = (Task) r;
        Future task2 = taskExec.submit(rr);
        try {
            task2.get(timeout, unit);
        } catch (ExecutionException ex) {
            throw ex;
        } catch (TimeoutException ex) {
            //will be cancelled below
            System.out.println(rr + " time-out!");
        } finally {
            if (!task2.isDone())
                System.out.println(rr + " was cancelled!");
            //if task completed, cancel() have no effect so it is safe to call here
            task2.cancel(true);
        }
    }
    
    private class Task implements Runnable {
        int id;

        public Task(int id) {
            this.id = id;
        }
        
        @Override
        public void run() {
            try {
                Thread.sleep(10);
                System.out.println(this + " has completed @" + (System.currentTimeMillis() - startTime));
            } catch (InterruptedException ex) {
                Logger.getLogger(CancellationEmulator.class.getName()).log(Level.SEVERE, null, ex);
            }
        }

        @Override
        public String toString() {
            return "Task[" + id + "]";
        }
    }
}
